package cz.maestro.sokodroid;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.opengl.GLUtils;

enum Direction {
	UP, DOWN, LEFT, RIGHT
};

public class Maze {
	private final int one = 65536;
	private final int of = 256;

	private Bitmap bmItems;
	private Bitmap bmWalls;

	private IntBuffer vbFloor;
	private IntBuffer tbFloor;
	private IntBuffer vbWall;
	private IntBuffer tbWall;

	private int floorCount = 0;
	private int wallCount = 0;
	private int cratesCount = 0;

	private Crate crate[];
	public Droid droid;

	private int mazeWidth;
	private int mazeHeight;
	private int maze[][];

	static public int ITEM_WALL = 1;
	static public int ITEM_FLOOR = 2;
	static public int ITEM_FINAL_FLOOR = 4;
	static public int ITEM_DROID = 8;
	static public int ITEM_CRATE = 16;

	private boolean isWall(int x, int y) {
		return (maze[y][x] & ITEM_WALL) > 0;
	}

	private boolean isCrate(int x, int y) {
		return (maze[y][x] & ITEM_CRATE) > 0;
	}

	private boolean isDroid(int x, int y) {
		return (maze[y][x] & ITEM_DROID) > 0;
	}

	private boolean isFloor(int x, int y) {
		return (maze[y][x] & ITEM_FLOOR) > 0;
	}

	private boolean isEmptyFloor(int x, int y) {
		return (maze[y][x] & ITEM_DROID) == 0 && (maze[y][x] & ITEM_CRATE) == 0
				&& isFloor(x, y);
	}

	private boolean isFinalFloor(int x, int y) {
		return (maze[y][x] & ITEM_FINAL_FLOOR) > 0;
	}

	private void moveObject(int x1, int y1, int x2, int y2) {
		int object = isCrate(x1, y1) ? ITEM_CRATE : ITEM_DROID;
		maze[y1][x1] &= ~object;
		maze[y2][x2] |= object;
	}

	public Maze(Context context, int level) {
		mazeWidth = Mazes.w[level];
		mazeHeight = Mazes.h[level];
		maze = new int[mazeHeight][mazeWidth];

		bmWalls = BitmapFactory.decodeResource(context.getResources(),
				R.drawable.walls);
		bmItems = BitmapFactory.decodeResource(context.getResources(),
				R.drawable.items);

		for (int y = 0; y < mazeHeight; y++) {
			for (int x = 0; x < mazeWidth; x++) {
				maze[y][x] = Mazes.maze[level][y][x];

				if (isWall(x, y))
					wallCount++;
				else if (isFloor(x, y))
					floorCount++;
				if (isCrate(x, y))
					cratesCount++;
			}
		}

		droid = new Droid(context);
		crate = new Crate[cratesCount];

		int i = 0;
		int j = 0;
		int k = 0;
		int vFloor[] = new int[floorCount * 12];
		int cFloor[] = new int[floorCount * 8];

		for (int y = 0; y < mazeHeight; y++) {
			for (int x = 0; x < mazeWidth; x++) {
				if (isFloor(x, y)) {
					// X						Y								Z
					vFloor[i++] = x * one;			vFloor[i++] = y * one;			vFloor[i++] = 0;
					vFloor[i++] = x * one;			vFloor[i++] = (y + 1) * one;	vFloor[i++] = 0;
					vFloor[i++] = (x + 1) * one;	vFloor[i++] = y * one;			vFloor[i++] = 0;
					vFloor[i++] = 1+(x + 1) * one;	vFloor[i++] = 1+(y + 1) * one;	vFloor[i++] = 0;
					if(isFinalFloor(x,y)) {
						// U						V
						cFloor[j++] = one/2+of;		cFloor[j++] = of;
						cFloor[j++] = one/2+of;		cFloor[j++] = one/2-of;
						cFloor[j++] = one-of;		cFloor[j++] = of;
						cFloor[j++] = one-of;		cFloor[j++] = one/2-of;
					}
					else {
						// U						V
						cFloor[j++] = of;			cFloor[j++] = one/2+of;
						cFloor[j++] = of;			cFloor[j++] = one-of;
						cFloor[j++] = one/2-of;		cFloor[j++] = one/2+of;
						cFloor[j++] = one/2-of;		cFloor[j++] = one-of;
					}
				}
				if(isCrate(x,y)) {
					crate[k] = new Crate();
					crate[k].setPos(x, y);
					k++;
				}
				if(isDroid(x,y)) {
					droid.setPos(x, y);
				}
			}
		}
		
		i = 0;
		j = 0;
		int vWall[] = new int[wallCount * 12 * 5];
		int cWall[] = new int[wallCount * 8 * 5];
		boolean p[] = {false, false, false, false, false};
		for (int y = 0; y < mazeHeight; y++) {
			for (int x = 0; x < mazeWidth; x++) {
				if (isWall(x, y)) {
					if(!isWall(x  , y+1)) p[1] = true; else p[1] = false;
					if(!isWall(x+1, y  )) p[2] = true; else p[2] = false;
					if(!isWall(x  , y-1)) p[3] = true; else p[3] = false;
					if(!isWall(x-1, y  )) p[4] = true; else p[4] = false;
					
					// X				Y								Z
					vWall[i++] = x * one;			vWall[i++] = y * one;			vWall[i++] = one/2;
					vWall[i++] = x * one;			vWall[i++] = (y + 1) * one ;		vWall[i++] = one/2;
					vWall[i++] = (x + 1) * one;		vWall[i++] = y * one;			vWall[i++] = one/2;
					vWall[i++] = (x + 1) * one;		vWall[i++] = (y + 1) * one;		vWall[i++] = one/2;
					
					if(p[1]) {
						vWall[i++] = x * one;			vWall[i++] = (y + 1) * one;		vWall[i++] = one/2;
						vWall[i++] = x * one;			vWall[i++] = (y + 1) * one;		vWall[i++] = 0;
						vWall[i++] = (x + 1) * one;		vWall[i++] = (y + 1) * one;		vWall[i++] = one/2;
						vWall[i++] = (x + 1) * one;		vWall[i++] = (y + 1) * one;		vWall[i++] = 0;
					}
					if(p[2]) {
						vWall[i++] = (x + 1) * one;		vWall[i++] = (y + 1) * one;		vWall[i++] = one/2;
						vWall[i++] = (x + 1) * one;		vWall[i++] = (y + 1) * one;		vWall[i++] = 0;
						vWall[i++] = (x + 1) * one;		vWall[i++] = y * one;			vWall[i++] = one/2;
						vWall[i++] = (x + 1) * one;		vWall[i++] = y * one;			vWall[i++] = 0;
					}
					if(p[3]) {
						vWall[i++] = (x + 1) * one;		vWall[i++] = y * one;			vWall[i++] = one/2;
						vWall[i++] = (x + 1) * one;		vWall[i++] = y * one;			vWall[i++] = 0;
						vWall[i++] = x * one ;			vWall[i++] = y * one;			vWall[i++] = one/2;
						vWall[i++] = x * one ;			vWall[i++] = y * one;			vWall[i++] = 0;
					}		
					if(p[4]) {
						vWall[i++] = x * one;			vWall[i++] = y * one;			vWall[i++] = one/2;
						vWall[i++] = x * one ;			vWall[i++] = y * one;			vWall[i++] = 0;
						vWall[i++] = x * one;			vWall[i++] = (y + 1) * one;		vWall[i++] = one/2;
						vWall[i++] = x * one;			vWall[i++] = (y + 1) * one;		vWall[i++] = 0;
					}
	
					// U							// V
					cWall[j++] = of;				cWall[j++] = of;
					cWall[j++] = of;				cWall[j++] = one/2 -of;
					cWall[j++] = one/2-of;			cWall[j++] = of;
					cWall[j++] = one/2-of;			cWall[j++] = one/2-of;

					for (k = 1; k < 5; k++) {
						if(p[k]) {
							wallCount++;
							cWall[j++] = one/2+of;		cWall[j++] = one/2+of;
							cWall[j++] = one/2+of;		cWall[j++] = one/2 + one/4;
							cWall[j++] = one-of;		cWall[j++] = one/2+of;
							cWall[j++] = one-of;		cWall[j++] = one/2 + one/4;
						}
					}
				}
			}
		}

		ByteBuffer vbF = ByteBuffer.allocateDirect(vFloor.length * 4);
		vbF.order(ByteOrder.nativeOrder());
		vbFloor = vbF.asIntBuffer();
		vbFloor.put(vFloor);
		vbFloor.position(0);

		ByteBuffer tbF = ByteBuffer.allocateDirect(cFloor.length * 4);
		tbF.order(ByteOrder.nativeOrder());
		tbFloor = tbF.asIntBuffer();
		tbFloor.put(cFloor);
		tbFloor.position(0);

		ByteBuffer vbW = ByteBuffer.allocateDirect(vWall.length * 4);
		vbW.order(ByteOrder.nativeOrder());
		vbWall = vbW.asIntBuffer();
		vbWall.put(vWall);
		vbWall.position(0);

		ByteBuffer tbW = ByteBuffer.allocateDirect(cWall.length * 4);
		tbW.order(ByteOrder.nativeOrder());
		tbWall = tbW.asIntBuffer();
		tbWall.put(cWall);
		tbWall.position(0);
	}

	public void draw(GL10 gl) {
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmWalls, 0);

		gl.glVertexPointer(3, GL10.GL_FIXED, 0, vbFloor);
		gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, tbFloor);
		gl.glColor4f(0.5f, 0.5f, 1, 1);
		gl.glNormal3f(0, 0, 1);
		for (int i = 0; i < floorCount * 4; i += 4) 
			gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, i, 4);

		gl.glVertexPointer(3, GL10.GL_FIXED, 0, vbWall);
		gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, tbWall);
		gl.glColor4f(0.5f, 0.5f, 1, 1);
		gl.glNormal3f(0, 0, 1);
		for (int i = 0; i < wallCount * 4; i += 4)
			gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, i, 4);

		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmItems, 0);
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

		droid.draw(gl);

		for (int i = 0; i < cratesCount; i++)
			crate[i].draw(gl);
	}

	private Crate getCrate(int x, int y) {
		for (Crate c : crate)
			if (c.x == x && c.y == y)
				return c;
		return null;
	}

	public int move(Direction dir) {
		return move(dir, false);
	}

	public int move(Direction dir, boolean zerotime) {
		Crate c = null;
		if (droid.bMoving && zerotime == false)
			return 0;

		int x = droid.x, y = droid.y;
		int nx = x, ny = y;
		int cx = x, cy = y;

		switch (dir) {
			case UP:	ny -= 1;	cy -= 2;	break;
			case DOWN:	ny += 1;	cy += 2;	break;
			case LEFT:	nx -= 1;	cx -= 2;	break;
			case RIGHT:	nx += 1;	cx += 2;	break;
		}
		c = getCrate(nx, ny);
		if (!isEmptyFloor(nx, ny) && (c == null || !isEmptyFloor(cx, cy))) {
			droid.move(x, y, null, dir);
			return -1;
		}

		if (c != null) {
			if (zerotime)
				c.setPos(cx, cy);
			else {
				c.x = cx;
				c.y = cy;
			}
			moveObject(nx, ny, cx, cy);
		}
		moveObject(x, y, nx, ny);
		if (zerotime)
			droid.setPos(nx, ny);
		else
			droid.move(nx, ny, c, dir);
		if (c == null)
			return 1;

		for (Crate cr : crate)
			if (!isFinalFloor(cr.x, cr.y))
				return 2;

		return 3;
	}
}

class Crate {
	private final IntBuffer vbCrate;
	private final IntBuffer tbCrate;
	
	public float fx;
	public float fy;
	
	public int x;
	public int y;

	public Crate() {
		int size = 16384 + 8192;
		int one  = 65536;
		int vertices[] = {
			-size, -size,  size,	 size, -size,  size,	-size,  size,  size,	 size,  size,  size,	// FRONT
			-size, -size, -size,	-size,  size, -size,	 size, -size, -size,	 size,  size, -size,	// BACK
			-size, -size,  size,	-size,  size,  size,	-size, -size, -size,	-size,	size, -size,	// LEFT
			 size, -size, -size,	 size,  size, -size,	 size, -size,  size,	 size,	size,  size,	// RIGHT
			-size,  size,  size,	 size,  size,  size, 	-size,  size, -size,	 size,	size, -size,	// TOP
			-size, -size,  size,	-size, -size, -size,	 size, -size,  size,	 size, -size, -size,	// BOTTOM 
		};
		ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
		vbb.order(ByteOrder.nativeOrder());
		vbCrate = vbb.asIntBuffer();
		vbCrate.put(vertices);
		vbCrate.position(0);

		int texCoords[] = {
			one/2, one/2, one, one/2, one/2, 0, one, 0,		// FRONT
			one, one/2, one, 0, one/2, one/2, one/2, 0,		// BACK
			one, one/2, one, 0, one/2, one/2, one/2, 0,		// LEFT
			one, one/2, one, 0, one/2, one/2, one/2, 0,		// RIGHT
			one, 0, one/2, 0, one, one/2, one/2, one/2,		// TOP
			one/2, 0, one/2, one/2, one, 0, one, one/2,		// BOTTOM
		};

		ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4);
		tbb.order(ByteOrder.nativeOrder());
		tbCrate = tbb.asIntBuffer();
		tbCrate.put(texCoords);
		tbCrate.position(0);
	}

	public void setPos(int x, int y){
		this.x = x;
		this.y = y;
		this.fx = x;
		this.fy = y;
	}

	public void draw(GL10 gl) {
		gl.glTranslatef(fx + 0.5f, fy + 0.5f, +0.375f);
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		gl.glVertexPointer(3, GL10.GL_FIXED, 0, vbCrate);
		gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, tbCrate);

		gl.glColor4f(1, 1, 1, 1);
		gl.glNormal3f(0, 0, 1);		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
		gl.glNormal3f(0, 0, -1);	gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);
		
		gl.glColor4f(1, 1, 1, 1);
		gl.glNormal3f(-1, 0, 0);	gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4);
		gl.glNormal3f(1, 0, 0);		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4);
		
		gl.glColor4f(1, 1, 1, 1);
		gl.glNormal3f(0, 1, 0);		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
		gl.glNormal3f(0, -1, 0);	gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);
		
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glTranslatef(- fx - 0.5f, -fy - 0.5f, -0.375f);
	}
}

class Droid {
	private final IntBuffer vbCrate;
	private final IntBuffer nbCrate;
	private final IntBuffer cbCrate;
	
	private MediaPlayer mp;
	
	private float fx;
	private float fy;
	
	public int x;
	public int y;
	
	public  boolean bMoving = false;
	
	private boolean bFirst = false;
	private int rotation = 180;
	private int neededRotation = 180;
	
	private Crate crate = null;

	public Droid(Context context) {
		int vertices[] = Vertex.getData();
		mp = MediaPlayer.create(context, R.raw.movesound);

		ByteBuffer vbb = ByteBuffer.allocateDirect(Vertex.size * 4 * 3);
		ByteBuffer nbb = ByteBuffer.allocateDirect(Vertex.size * 4 * 3);
		ByteBuffer cbb = ByteBuffer.allocateDirect(Vertex.size * 4 * 3);
		cbb.order(ByteOrder.nativeOrder());
		vbb.order(ByteOrder.nativeOrder());
		nbb.order(ByteOrder.nativeOrder());
		
		vbCrate = vbb.asIntBuffer();
		nbCrate = nbb.asIntBuffer();
		cbCrate = cbb.asIntBuffer();

		for (int i = 0; i < vertices.length / 8; i++) {
			vbCrate.put(vertices[i*8+0]);
			vbCrate.put(vertices[i*8+1]);
			vbCrate.put(vertices[i*8+2]);
			nbCrate.put(vertices[i*8+3]);
			nbCrate.put(vertices[i*8+4]);
			nbCrate.put(vertices[i*8+5]);
			cbCrate.put(vertices[i*8+6]);
			cbCrate.put(vertices[i*8+7]);
		}
		vbCrate.position(0);
		nbCrate.position(0);
		cbCrate.position(0);
	}

	public void setPos(int x, int y) {
		this.fx = x;
		this.fy = y;
		this.x = x;
		this.y = y;
	}

	public void move(int x, int y, Crate c, Direction dir) {
		bMoving = true;
		bFirst = true;
		this.x = x;
		this.y = y;
		crate = c;
		
		switch(dir)	{
			case DOWN:	neededRotation = 180;	break;
			case UP:	neededRotation = 0;		break;
			case LEFT:	neededRotation = 270;	break;
			case RIGHT:	neededRotation = 90;	break;
		}
	}

	public void draw(GL10 gl) {
		float movingSpeed = 0.125f;

		if (bMoving && neededRotation != rotation) {
			if ((neededRotation - rotation + 360) % 360 < 180)
				rotation = (rotation + 18) % 360;
			else
				rotation = (rotation + 342) % 360;
		} else if (bMoving) {
			fx += movingSpeed * Math.signum(x - fx);
			fy += movingSpeed * Math.signum(y - fy);
			if (crate != null) {
				if (bFirst && !mp.isPlaying()) {
					bFirst = false;
					try {
						mp.prepare();
					} catch (Exception e) {
					}
					mp.start();
				}
				crate.fx = crate.x + fx - x;
				crate.fy = crate.y + fy - y;
			}
			if (Math.abs(x - fx) < movingSpeed
					&& Math.abs(y - fy) < movingSpeed) {
				bMoving = false;
				if (crate != null) {
					crate.fx = crate.x;
					crate.fy = crate.y;
				}
			}
		}

		gl.glTranslatef(fx + 0.5f, fy + 0.5f, +0.5f);
		gl.glRotatef(rotation, 0, 0, 1);

		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

		gl.glVertexPointer(3, GL10.GL_FIXED, 0, vbCrate);
		gl.glNormalPointer(GL10.GL_FIXED, 0, nbCrate);
		gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, cbCrate);

		gl.glDrawArrays(GL10.GL_TRIANGLES, 0, Vertex.size);

		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

		gl.glRotatef(-rotation, 0, 0, 1);
		gl.glTranslatef(-fx - 0.5f, -fy - 0.5f, -0.5f);
	}
}
